#include "gx_hash.h"
#include "gx_primetable.h"
#include "gx_err.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

HASH* hash_new(int nrows,unsigned keysize)
{
	HASH *h;

	if(nrows > 0)
	{
		nrows = nearest_prime(nrows);
	}
	else
	{
		nrows = -nrows;
	}

	h = (HASH*)malloc(sizeof(HASH));

	if(!h)
		return NULL;

	memset(h,0,sizeof(HASH));

	h->hashfcn = hashfcn_new(nrows);

	if(!h->hashfcn)
	{
		free(h);
		return NULL;
	}
	
//	hashfcn_static(h->hashfcn);

	h->table = (HASH_NODE**)malloc(sizeof(HASH_NODE*)*nrows);

	if(!h->table)
	{
		hashfcn_free(h->hashfcn);
		free(h);
		return NULL;
	}

	for(int i=0;i<nrows;i++)
	{
		h->table[i] = NULL;
	}
	h->nrows = nrows;
	h->count = 0;
	h->crow = 0;
	h->cnode = 0;
	h->keysize = keysize;
	return h;
}

SET* set_new(int nrows,unsigned keysize)
{
	return hash_new(nrows,keysize);
}


void  hash_delete(HASH *h,void (*free_key)(void *),void(*free_data)(void*))
{
	HASH_NODE *node,*tmp;

	if(!h)
		return ;

	hashfcn_free(h->hashfcn);

	if(h->table)
	{
		for(int i=0;i<h->nrows;i++)
		{
			for(node=h->table[i];node;)
			{
				tmp = node;
				node= node->next;

				if(free_key&&tmp->key)
				{
					free_key(tmp->key);
				}
				if(free_data&&tmp->data)
					free_data(tmp->data);
				free(tmp);
			}
		}
		free(h->table);
		h->table=NULL;
	}
	free(h);
}

void set_delete(SET *h,void (*free_key)(void *))
{
 	hash_delete(h,free_key,NULL);
}


int hash_add(HASH *t,void *key,void *data)
{
	unsigned hashkey;
	HASH_NODE *hnode;
	unsigned index = 0;

	hashkey = t->hashfcn->hash_fcn(t->hashfcn,(unsigned char*)key,t->keysize==0 ? strlen((char*)key):t->keysize);

	index = hashkey % t->nrows;

	for(hnode=t->table[index];hnode;hnode=hnode->next)
	{
		if(t->keysize != 0)
		{
			if(!t->hashfcn->keycmp_fcn(hnode->key,key,t->keysize))
			{
				t->cnode = hnode;
				return DATA_EXIT;		
			}
		}
		else
		{
			if(!strcmp((const char*)hnode->key,(const char*)key))
			{
				t->cnode=hnode;
				return DATA_EXIT;
			}
		}
	}

	hnode = (HASH_NODE*)malloc(sizeof(HASH_NODE));

	if(!hnode)
		return NOMEM;

	hnode->key = key;

	if(t->table[index])
	{
		hnode->prev = 0;
		hnode->next = t->table[index];
		hnode->data = data;
		t->table[index]->prev = hnode;
		t->table[index] = hnode;
	}
	else
	{
		hnode->prev = 0;
		hnode->next = 0;
		hnode->data = data;
		t->table[index] = hnode;
	}

	t->count++;
	return SUCCESS;
}

int set_add(SET *s,void *key)
{
	return hash_add(s,key,NULL);
}


static HASH_NODE* hash_find_node(HASH *t,void*key)
{
	unsigned hashkey;
	int index;
	HASH_NODE *hnode;

	hashkey = t->hashfcn->hash_fcn(t->hashfcn,(unsigned char*)key,t->keysize==0?strlen((char*)key):t->keysize);

	index = hashkey % t->nrows;

	for( hnode=t->table[index];hnode;hnode=hnode->next)
	{
		if(t->keysize != 0)
		{
			if(!t->hashfcn->keycmp_fcn(hnode->key,key,t->keysize))
			{
				//printf("%s\n",(char*)hnode->key);
				return hnode;
			}
		}
		else
		{
			if(!strcmp((char*)hnode->key,(char*)key))
			{
				return hnode;
			}
		}
	}

	return NULL;
}

void* hash_find(HASH *t,void *key)
{
	HASH_NODE *hnode;

	hnode = hash_find_node(t,key);

	if(hnode)
		return hnode->data;

	return NULL;
}


int set_find(SET *s,void *key)
{
	if(hash_find_node(s,key))
		return 1;
	else 
		return 0;
}


static int hash_free_node(HASH *t,unsigned index,HASH_NODE *hnode,void (*free_key)(void*),void (*free_data)(void*))
{
	if(hnode->key)
	{
		if(free_key)
			free_key(hnode->key);
		hnode->key=0;
	}

	if(hnode->data)
	{
		if(free_data)
			free_data(hnode->data);
		hnode->data=0;
	}

	if(hnode->prev)
	{
		hnode->prev->next = hnode->next;
		if( hnode->next ) 
			hnode->next->prev = hnode->prev;
	}
	else if(t->table[index])
	{
		t->table[index] = t->table[index]->next;
		if( t->table[index] )
			t->table[index]->prev = 0;
	}
	free(hnode);
	t->count--;
	return SUCCESS;
}

int hash_remove(HASH *t ,void* key,void (*free_key)(void*),void(*free_data)(void*))
{
	HASH_NODE *hnode;
	unsigned hashkey,index;

	hashkey = t->hashfcn->hash_fcn(t->hashfcn,(unsigned char*)key,t->keysize==0?strlen((char*)key):t->keysize);

	index = hashkey % t->nrows;

	for( hnode= t->table[index];hnode;hnode=hnode->next)
	{
		if(t->keysize != 0)
		{
			if(!t->hashfcn->keycmp_fcn(hnode->key,key,t->keysize))
			{
				return hash_free_node(t,index,hnode,free_key,free_data);
			}
		}
		else
		{
			if(!strcmp((const char*)hnode->key,(const char*)key))
			{
				return hash_free_node(t,index,hnode,free_key,free_data);
			}
		}
	}

	return NODATA;
}

int set_remove(SET *t ,void* key,void (*free_key)(void*))
{
	return hash_remove(t,key,free_key,NULL);
}

static void hash_next(HASH *t)
{
	if(!t->cnode)
		return ;

	t->cnode = t->cnode->next;

	if(t->cnode)
	{
		return ;
	}

	for(t->crow++;t->crow < t->nrows;t->crow++)
	{
		t->cnode = t->table[t->crow];
		if(t->cnode)
		{
			return ;
		}
	}
}

HASH_NODE* hash_findfirst(HASH *t)
{
	HASH_NODE *n;

	for(t->crow=0; t->crow<t->nrows; t->crow++)
	{
		t->cnode = t->table[t->crow];

		if(t->cnode)
		{
			n = t->cnode;
			hash_next(t);
			return n;
		}
	}

	return NULL;
}

HASH_NODE* hash_findnext(HASH *t)
{
	HASH_NODE *n;

	n = t->cnode;

	if(!n)
	{
		return NULL;
	}
	hash_next(t);
	return n;
}

int hash_set_keyops(HASH*h,unsigned (*hash_fcn)(HASHFCN *p,unsigned char *d,int n),int (*keycmp_fcn)(const void *s1,const void *s2,size_t n))
{
	if(h && hash_fcn && keycmp_fcn)
	{
		return hashfcn_set_keyops(h->hashfcn,hash_fcn,keycmp_fcn);
	}
	return PARAMETER_ERROR;
}
